/**********************************************************************************************************************
 *
 * Project:    mGameEngine
 * File:       water.cpp
 * Author:     karooolek
 * Created on: Dec 18, 2009
 *
 **********************************************************************************************************************/

#include "water.h"

namespace mGameEngine
{
namespace Game
{

using namespace Graphics;

Water::Water() :
    Drawable(),
    _timer(),
    _amplitude(), _length(99999.9f, 99999.9f, 99999.9f, 99999.9f),
    _steepness(), _velocity(0.0f),
    _color(0.0f, 0.1f, 0.2f),
    _envMap(NULL),
    _normalMap(NULL), _normalMapSize(1.0f, 1.0f), _normalMapScale(1.0),
    _foam(NULL), _foamSize(1.0f, 1.0f), _foamMinMax(0.0f, 1.0f),
    _fresnel(0.0f, 1.0f, 5.0f)

{
    addDrawable(this);

    _boundingSphere.setRadius(999999999999.9f);
}

Water::~Water()
{
}

void Water::draw() const
{
    static GPUProgram gpuProgram(
#include "water.vert"
        ,
#include "water.frag"
        , true);

    // bind gpu program
    gpuProgram.use();

    gpuProgram.setValue("t", _timer.getTime());
    gpuProgram.setVector("a", _amplitude);
    gpuProgram.setVector("l", _length);
    gpuProgram.setVector("q", _steepness);
    gpuProgram.setMatrix("vel", _velocity);

    gpuProgram.setVector("color", Vector4(_color));

    gpuProgram.setTexture("envMap", _envMap, 4);
    gpuProgram.setVector("envColor", Vector4(_envColor));

    gpuProgram.setTexture("normalMap", _normalMap, 1);
    gpuProgram.setVector("normalMapSize", Vector2(1.0f, 1.0f) / _normalMapSize);
    gpuProgram.setValue("normalMapScale", _normalMapScale);

    gpuProgram.setTexture("foam", _foam, 3);
    gpuProgram.setVector("foamSize", Vector2(1.0f, 1.0f) / _foamSize);
    gpuProgram.setVector("foamMinMax", _foamMinMax);

    gpuProgram.setVector("fresnel", _fresnel);

    // render
    static uint dl = 0;
    if(!dl)
    {
        dl = glGenLists(1);
        glNewList(dl, GL_COMPILE);

        static const float res = 0.01f;
        for(float x = -1.2f; x <= 1.2f; x += res)
        {
            //glLineWidth(1.5f);
            //glPolygonMode(GL_FRONT, GL_LINE);
            glBegin(GL_TRIANGLE_STRIP);
            for(float y = -1.2f; y <= 1.2f; y += res)
            {
                glVertex2fv(Vector2(x, y));
                glVertex2fv(Vector2(x + res, y));
            }
            glEnd();
            //glPolygonMode(GL_FRONT, GL_FILL);
        }

        glEndList();
    }
    glCallList(dl);

    /*

    // draw heightfield
    StandardMaterial mat;
    mat.setEmissiveColor(Color(1.0f, 0.0f, 0.0f, 1.0f));
    mat.set();


    glDisable(GL_DEPTH_TEST);
    glPolygonMode(GL_FRONT, GL_LINE);
    const float r= 0.1f;
    const Vector3 &pos = _heightCache.getPosition();
    const Vector3 &sz = _heightCache.getSize();
        for(Vector3 p = pos; p.x <= pos.x + sz.x; p.x += r)
        {
            glBegin(GL_TRIANGLE_STRIP);
            for(p.y = pos.y; p.y <= pos.y + sz.y; p.y += r)
            {
                p.z = _heightCache.getHeightAt(p);
                Vector3 p2 = p + Vector3(r, 0.0f, 0.0f);
                p2.z = _heightCache.getHeightAt(p2);

                glVertex3fv(p);
                glVertex3fv(p2);
            }
            glEnd();
        }
    glPolygonMode(GL_FRONT, GL_FILL);
    glEnable(GL_DEPTH_TEST);
    */
}

Vector3 Water::_calcGerstner(const Vector2 &position) const
{
    // get time
    float t = _timer.getTime();

    // sum four gerstner waves
    Vector3 p(position.x, position.y, 0.0f);;
    for(int i = 0; i != 4; ++i)
    {
        // direction and velocity
        Vector2 dir = -Vector2(_velocity.m[i]);
        float v = length(dir);
        dir.normalize();
        
        float w = 2.0f * M_PI / _length[i];
        float wa = w * _amplitude[i];
        float qw = _steepness[i] / w;
        float qwa = _steepness[i] / wa;
        float d = dot(dir, position);
        float x = w * (t * v + d);
        float cosx = cos(x);
        float cosxx = cosx * dir.x;
        float cosxy = cosx * dir.y;
        float sinx = sin(x);
        float sinxx = sinx * dir.x;

        // wave position
        p.x += qw * cosxx;
        p.y += qw * cosxy;
        p.z += _amplitude[i] * sinx;
    }

    return p;
}

float Water::getHeightAt(const Vector2 &p) const
{
    return _calcGerstner(p).z;
    
    // TODO cache in heightmap
    // get from heightmap
}

Vector3 Water::getNormalAt(const Vector2 &p) const
{    
    // calculate normal
    float h = getHeightAt(p);
    Vector3 dx(0.1f, 0.0f, getHeightAt(p + Vector2(0.1f, 0.0f)) - h);
    Vector3 dy(0.0f, 0.1f, getHeightAt(p + Vector2(0.0f, 0.1f)) - h);
    Vector3 n = cross(dx.normalize(), dy.normalize());
    return n;
}

float Water::getSlopeAt(const Vector2 &p) const
{
    // calculate slope
    Vector3 n = getNormalAt(p);
    float slope = acosf(dot(n, Vector3(0.0f, 0.0f, 1.0f)));
    return slope;
}

}
}

